/**
 * This software is released under the terms of the MIT License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @copyright  2009-2011 Roberto Perpuly
 * @license    http://www.opensource.org/licenses/mit-license.php The MIT License
 */
#ifndef __parserclass__
#define __parserclass__

#include <language/LexicalAnalyzerClass.h>
#include <wx/string.h>
#include <unicode/unistr.h>

namespace mvceditor {

/**
 * The parser class is designed in a way that can utilized by different pieces of code.  The parser will analyze
 * given code and make calls to the different registered observers.  There are observers for classes, functions, and 
 * methods. Not all observers have to be set; for example if a FunctionObserverClass is never registered then the 
 * parse will not notify when a function has been found in a piece of code.
 * 
 * @code
 *   class EchoAndObserverClass : public ClassObserverClass {
 * 
 *     virtual void ClassFound(const UnicodeString& className, const UnicodeString& signature, 
 *          const UnicodeString& comment) {
 *       printf("Found Class %s\n", (const char*)className.ToUTF8());
 *     }
 *   }
 * 
 *   EchoAndObserverClass echoObserver;
 *   ParserClass parser;
 *   parser.SetClassObserver(&echoObserver);
 *   wxString someFileName = wxT("/some/file.php");
 *   if (!parser.ScanFile(someFileName)) {
 *     puts("Could not find file to parse!");
 *   }
 * @endcode
 * 
 * Observers are very lenient.  They are not meant to be used to check code for errors, it is only meant to scan files for 
 * specific resources (classes, methods, functions, ...)
 *
 * Lint functionality
 * 
 * The parser class has the ability to check PHP code for syntax errors. This is done via the Lint() method.
 * 
 * @code
 *   ParserClass parser;
 *   wxString file = wxT("/path/to/phpfile.php");
 *   LintResultsClass lintResults;
 *   if (parser.LintFile(file, parserResults)) {
 *     printf("No syntax errors in file %s", (const char*)file.ToAscii());
 *   }
 *   else {
 *     printf("%s. Error found in file %s on line %d.\n", parserResults.Error, (const char*)file.ToAscii(), parserResults.LineNumber);
 *   }
 * @encode
 */
class ClassObserverClass;
class ClassMemberObserverClass;
class FunctionObserverClass;
class VariableObserverClass;

/**
 * Holds the results of the lint check.  Currently lint check will stop when 
 * the first error is encountered.
 */
class LintResultsClass {
public:

	/**
	 * A short description of the error; this is generated by the bison parser and is
	 * not the most user-friendly but it is exactly what PHP displays; might as well
	 * keep it consistant.
	 */
	UnicodeString Error;

	/**
	 * Path to the file in which the error ocurred. 
	 * This is what was given to the LintFile() method.
	 * For LintString() results this will be the empty string.
	 */
	wxString File;

	/**
	 * The line in which the error ocurred. This is 1-based.
	 */
	int LineNumber;

	/**
	 * The character offset in which the error ocurred (with regargs to the start of
	 * the file). This is 0-based.
	 */
	int CharacterPosition;

	LintResultsClass();

	/**
	 * copy the attributes from src to this object.
	 */
	void Copy(const LintResultsClass& src);
};

class ParserClass {


public:
	
	ParserClass();
	
	/**
	 * Opens and scans the given file; calling the proper observers when it encounters
	 * a class, function, or variable declaration.
	 * 
	 * @param const wxString& file the file to parse.  Must be a full path.
	 * @return bool if file was found.
	 */
	bool ScanFile(const wxString& file);
	
	/**
	 * Scans the given string; calling the proper observers when it encounters
	 * a class, function, or variable declaration.
	 * 
	 * @param const UnicodeString& code the code to parse.
	 * @return bool alway true for now, just to give this method symmetry with ParseFile() method.
	 */
	bool ScanString(const UnicodeString& code);
	
	/**
	 * Set the class observer.  The observer will get notified when a class is encountered.
	 * Memory management of this pointer should be done by the caller.
	 * 
	 * @param ClassObserverClass* observer the object to sent notifications to 
	 */
	void SetClassObserver(ClassObserverClass* observer);
	
	/**
	 * Set the class member observer.  The observer will get notified when a class member is encountered.
	 * Memory management of this pointer should be done by the caller.
	 * 
	 * @param ClassMemberObserverClass* observer the object to sent notifications to 
	 */
	void SetClassMemberObserver(ClassMemberObserverClass* observer);
	
	/**
	 * Set the function observer.  The observer will get notified when a function is encountered.
	 * Memory management of this pointer should be done by the caller.
	 * 
	 * @param FunctionObserverClass* observer the object to sent notifications to 
	 */
	void SetFunctionObserver(FunctionObserverClass* observer);
	
	/**
	 * Set the variable observer.  The observer will get notified when a new variable has been created.
	 * Memory management of this pointer should be done by the caller.
	 * 
	 * @param VariableObserverClass* observer the object to sent notifications to 
	 */
	void SetVariableObserver(VariableObserverClass* observer);
	
	/**
	 * Perform a TRUE PHP syntax check on the entire file. This syntax check is based on PHP 5.3.
	 * Note that this is not entirely the same as 'php -l' command; the PHP lint command detects 
	 * duplicate function  / class names where as this lint check method does not.
	 *
	 * Returns true if the file had no syntax errors. Note that a file that does not have
	 * any PHP code will be considered a good file (a PHP file that has only HTML is
	 * considered good and true will be returned).
	 * 
	 * @param const wxString& file the file to parse.  Must be a full path.
	 * @param LintResultsClass& results any error message will be populated here
	 * @return bool true if file was found and had no syntax errors.
	 */
	bool LintFile(const wxString& file, LintResultsClass& results);
	
	/**
	 * Perform a syntax check on the given source code. Source code is assumed to be
	 * all code (HTML will not be skipped, and will result in syntax errors). The PHP 
	 * open tag is optional.
	 * Returns true if the code had no syntax errors.
	 * 
	 * @param const UnicodeString& code the actual code to parse.
	 * @param LintResultsClass& results any error message will be populated here
	 * @return bool true if the code has no syntax errors.
	 */
	bool LintString(const UnicodeString& code, LintResultsClass& results);
	
private:

	/**
	 * Loops through all of the lexer's tokens, notifying the different observers when classes, methods, functions 
	 * are found.
	 */
	void Scan();
	
	/**
	 * Scans a class declaration ("class XXXX extends BBBB implements CCCC, DDDDD {" ), eating up to and including 
	 * tokens until the open brace.
	 * Notifies the class observer, if registered.
	 * 
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the class
	 * @return UnicodeString the class that was parsed.
	 */
	UnicodeString ScanClass(const UnicodeString& phpDocComment);

	/**
	 * Scans a method declaration ("protected function getName($XXX) {" ), eating up tokens up to and including the 
	 * open brace. Notifies the class member observer, if registered.
	 * 
	 * @param const UnicodeString& className the class name the method is in
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the method
	 * @return UnicodeString the name of the method that was parsed.
	 */
	UnicodeString ScanMethod(const UnicodeString& className, const UnicodeString& phpDocComment);

	/**
	 * Scans a member declaration ("protected $name;" ), eating up tokens up to and including the semicolon ';'
	 * Notifies the class member observer, if registered.
	 * 
	 * @param const UnicodeString& className the class name the method is in
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the member
	 */
	void ScanMemberVariable(const UnicodeString& className, const UnicodeString& phpDocComment);
		
	/**
	 * Scans a constant declaration ("const NAME='';"), eating up tokens up to and including the semicolon ';'
	 * Notifies the class observer, if registered.
	 * 
	 * @param const UnicodeString& className the class name the method is in
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the constant
	 */
	void ScanConstantDeclaration(const UnicodeString& className, const UnicodeString& phpDocComment);
	
	/**
	 * Scans all variables declarations inside of a code block (parsing will stop at the end of the 
	 * block). For this method tow work correctly, Lexer must be located at the open brace. Will notify the 
	 * VariableObserver when each variable declaration is found. The lexer will be left at the end brace position, 
	 * such that a call to Lexer.GetLexeme() will return '}' and Lexer.NextToken() will return the token after the 
	 * end brace.
	 * 
	 * @param const UnicodeString& className the class name of the current block scope
	 * @param const UnicodeString& functionName the function name of the block scope
	 */
	void ScanVariableDeclarations(const UnicodeString& className, const UnicodeString& functionName);
	
	/**
	 * Parses a define declaration ("define('CONST', 'ff')" ), eating up tokens up to and including the semicolon ';'
	 * Notifies the define observer, if registered.
	 * 
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the define statement
	 */
	void ScanDefineDeclaration(const UnicodeString& phpDocComment);
	
	/**
	 * Get the return type from the '@return' / '@var' annotation
	 * 
	 * @param const UnicodeString& phpDocComment the comment
	 * @param bool varAnnotation if false, will return the word after '@var', else return the word after '@return'
	 * @return UnicodeString
	 */
	UnicodeString ReturnTypeFromPhpDocComment(const UnicodeString& phpDocComment, bool varAnnotation = false);

	/**
	 * Scans a function declaration ("function getName($XXX) {" ), eating up tokens up to and including the open brace.
	 * Notifies the function observer, if registered.
	 * 
	 * @param const UnicodeString& phpDocComment the phpDOC comment for the method
	 * @return UnicodeString the name of the method that was parsed.
	 */
	UnicodeString ScanFunction(const UnicodeString& phpDocComment);
	
	/**
	 * Gets the next token from the input being parsed. 
	 * 
	 * @return bool false if the EOF token has been reached (or there are no more tokens).
	 */
	bool Advance();
	
	/**
	 * Check for the existence of more tokens from the input soruce
	 * 
	 * @return bool false if the EOF token has been reached (or there are no more tokens).
	 */
	bool MoreTokens();

	/**
	 * Used to tokenize code
	 * 
	 * @var LexicalAnalyzerClass
	 */
	LexicalAnalyzerClass Lexer;
	
	/**
	 * Notify the ClassObserver when a class has been found. Memory management of this pointer should be
	 * done by the caller.
	 * 
	 * @var ClassObserverClass*
	 */
	ClassObserverClass* ClassObserver;

	/**
	 * Notify the ClassMemberObserver when a class member has been found. Memory management of this pointer should be
	 * done by the caller.
	 * 
	 * @var ClassMemberObserverClass*
	 */
	ClassMemberObserverClass* ClassMemberObserver;
	
	/**
	 * Notify the FunctionObserver when a function has been found. Memory management of this pointer should be
	 * done by the caller.
	 * 
	 * @var ClassObserverClass*
	 */	
	FunctionObserverClass* FunctionObserver;
	
	/**
	 * Notify the VariableObserver when a variable has been created. Memory management of this pointer should be
	 * done by the caller.
	 * 
	 * @var VariableObserverClass*
	 */		
	VariableObserverClass* VariableObserver;
	
	/**
	 * The lexeme currenly being parsed
	 * @var UnicodeString
	 */
	UnicodeString Lexeme;
	
	/**
	 * The lexeme one past the currenly being parsed
	 * @var UnicodeString
	 */
	UnicodeString LookaheadLexeme;
	
	/**
	 * The lexeme twice past the currenly being parsed
	 * @var UnicodeString
	 */
	UnicodeString NextLookaheadLexeme;

	/**
	 * The token currently being parsed
	 * @var int
	 */
	int Token;
	
	/**
	 * The token one past the currently being parsed
	 * @var int
	 */
	int LookaheadToken;
	
	/**
	 * The token twice past the currently being parsed
	 * @var int
	 */
	int NextLookaheadToken;
	
	/**
	 * The number of nested braces encountered.  This helps determine in we are inside of a class or function.
	 * 
	 * @var int 0 = global scope 
	 *          1 = class or function scope 
	 *          2 = method scope
	 */
	int BraceDepth;
};


/**
 * Interface to inherit from when needing to be notified when a class structure is encountered
 */
class ClassObserverClass {

public:
	/**
	 * Override this method to perform any custom logic when a class is found.
	 * 
	 * @param const UnicodeString& className the name of the class that was found
	 * @param const UnicodeString& signature the list of classes that the class inherits / implements in code format
	 *        for example "extends UserClass implements Runnable"
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void ClassFound(const UnicodeString& className, const UnicodeString& signature, 
		const UnicodeString& comment) = 0;
	
	/**
	 * Override this method to perform any custom logic when a define declaration is found.
	 * 
	 * @param const UnicodeString& variableName the name of the defined variable
	 * @param const UnicodeString& variableValue the variable value
	 * @param const UnicodeString& comment PHPDoc attached to the define
	 */
	virtual void DefineDeclarationFound(const UnicodeString& variableName, const UnicodeString& variableValue, 
		const UnicodeString& comment) = 0;
	
};

/**
 * Interface to inherit from when needing to be notified when a class member (either variable or method)
 * is encountered.
 */
class ClassMemberObserverClass {

public:
	/**
	 * Override this method to perform any custom logic when a class method is found.
	 * 
	 * @param const UnicodeString& className the name of the class that was found
	 * @param const UnicodeString& methodName the name of the method that was found
	 * @param const UnicodeString& signature string containing method parameters.  String is normalized, meaning that
	 *        any extra white space is removed, and every token is separated by one space only. ie. for the code
	 *        "public function doWork( $item1,   $item2  ) " the signature will be  "($item1, $item2)"
	 * @param const UnicodeString& returnType the method's return type, as dictated by the PHPDoc comment
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void MethodFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& signature, const UnicodeString& returnType, const UnicodeString& comment) = 0;
	
	/**
	 * Override this method to perform any custom logic when a class property is found.
	 * 
	 * @param const UnicodeString& className the name of the class that was found
	 * @param const UnicodeString& propertyName the name of the property that was found
	 * @param const UnicodeString& propertyType the property's type, as dictated by the PHPDoc comment
	 * @param const UnicodeString& comment PHPDoc attached to the property
	 * @param bool isConst true if property is a constant
	 */
	virtual void PropertyFound(const UnicodeString& className, const UnicodeString& propertyName, 
		const UnicodeString& propertyType, const UnicodeString& comment, bool isConst) = 0;
};

/**
 * Interface to inherit from when needing to be notified when a function is encountered.
 */
class FunctionObserverClass {

public:
	/**
	 * Override this method to perform any custom logic when a function is found.
	 * 
	 * @param const UnicodeString& functionName the name of the method that was found
	 * @param const UnicodeString& signature string containing method parameters.  String is normalized, meaning that
	 *        any extra white space is removed, and every token is separated by one space only. ie. for the code
	 *        "public function doWork( $item1,   $item2  ) " the signature will be  "($item1, $item2)"
	 * @param const UnicodeString& returnType the function's return type, as dictated by the PHPDoc comment
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void FunctionFound(const UnicodeString& functionName, 
		const UnicodeString& signature, const UnicodeString& returnType, const UnicodeString& comment) = 0;
	
};

/**
 * Interface to inherit from when needing to be notified when a variable is encountered.
 */
class VariableObserverClass {

public:
	/**
	 * Override this method to perform any custom logic when a 'new' variable declaration is found.
	 * 
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped 
	 *        inside a function or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty if 
	 *        variable is globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& returnType the variable's type, as dictated by  the 'new' declaration 
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void VariableCreatedWithNewFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName, const UnicodeString& returnType, const UnicodeString& comment) = 0;
		
	/**
	 * Override this method to perform any custom logic when a variable assignment from a function is found.
	 * 
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped 
	 *        inside a function or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty if
	 *        variable is globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& functionCalledName the name of the function that was used to create the variable
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void VariableCreatedWithFunctionFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName,  const UnicodeString& functionCalledName, const UnicodeString& comment) = 0;	

	/**
	 * Override this method to perform any custom logic when a variable declaration is found.
	 * 
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped 
	 *        inside a function or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty is 
	 *        variable is globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& methodCalledName the method name only (not prepended with class)
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void VariableCreatedWithThisMethodFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName, const UnicodeString& methodCalledName, const UnicodeString& comment) = 0;	
		
	/**
	 * Override this method to perform any custom logic when a variable declaration is found.
	 *
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped 
	 *        inside a function or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty is 
	 *        variable is globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& returnType the variable's type, as dictated by  the 'new' declaration 
	 * @param const UnicodeString& comment PHPDoc attached to the class
	 */
	virtual void VariableCreatedWithObjectMethodFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName, const UnicodeString& objectName, const UnicodeString& objectMethod, 
		const UnicodeString& comment) = 0;
		
	/**
	 * Override this method to perform any custom logic when a variable declaration is found. ($var1 = $var2)
	 *
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped inside a function
	 * 	      or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty is variable is 
	 *        globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& sourceVariable the variable's type, as dictated by  the 'new' declaration 
	 * @param const UnicodeString& comment PHPDoc attached to the variable
	 */
	virtual void VariableCreatedWithAnotherVariableFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName, const UnicodeString& sourceVariable, const UnicodeString& comment) = 0;
		
	/**
	 * Override this method to perform any custom logic when a variable declaration is found. ($var1 = 'hello')
	 *
	 * @param const UnicodeString& className class where the variable was found. may be empty is variable is scoped 
	 *       inside a function or is global.
	 * @param const UnicodeString& methodName function/method name where the variable was found. may be empty is 
	 *        variable is globally scoped.
	 * @param const UnicodeString& variableName the name of the variable that was found
	 * @param const UnicodeString& constant the variable value
	 * @param const UnicodeString& comment PHPDoc attached to the variable
	 */
	virtual void VariableConstantFound(const UnicodeString& className, const UnicodeString& methodName, 
		const UnicodeString& variableName, const UnicodeString& constant, const UnicodeString& comment) = 0;
};
}
#endif // __parserclass__
